현재 코드는 어플리케이션 생성 방식에 문제가 있다. @Before메소드가 테스트 메소드 개수만큼 반복되기 때문에 어플리케이션 컨텍스트도 세 번 만들어진다. 이는 빈이 많아지고 복잡해지면 어플리케이션 컨텍스트 생성에 적지 않은 시간이 소요 될 수 있고, 어떤 빈은 독자적으로 많은 리소스를 할당하거나 스레드를 띄우기도 할 수 있기 때문에 깔끔히 정리해주지 않으면 문제가 일어날 수 있다.

일반적으로 테스트는 가능한 독립적으로 새로운 오브젝트를 만들어 사용하는 것이 원칙이지만, 어플리케이션 컨텍스트 같은 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트를 만들기도 한다.

그러므로 스태틱 필드에 어플리케이션 컨텍스트를 저장해두어 각각의 오브젝트들이 접근할 수 있게 만든다.

테스트를 위한 어플리케이션 컨텍스트 관리

스프링은 JUnit을 이용하는 테스트 컨텍스트 프레임워크를 제공한다. 이 지원을 받으면 간단한 어노테이션 설정만으로 테스트에서 필요로 하는 어플리케이션 컨텍스트를 만들어서 모든 테스트가 공유할 수 있다.

스프링 테스트 컨텍스트 프레임워크 적용

  • @RunWith
    JUnit 프레임워크의 테스트 실행 방법을 확장할 때 사용하는 어노테이션. SpringJunit4ClassRunner를 지정해주면, 테스트가 사용할 어플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다.
  • @ContextConfiguration 자동으로 만들어줄 어플리케이션 컨텍스트의 설정파일 위치를 지정한 것.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/application-context.xml")
public class JUnitTest {
    @Autowired
    private ApplicationContext context;
    ... 
}

테스트 메소드의 컨텍스트 공유

위의 설정으로 하나의 어플리케이션 컨텍스트가 만들어져 모든 테스트 메소드에서 사용되고 있음을 알 수 있다. 스프링의 JUnit 확장 기능은 테스트가 실행되기 전에 딱 한번만 어플리케이션 컨텍스트를 만들어두고, 테스트 오브젝트가 만들어질 때마다 특정 필드에 주입해준다. 일종의 DI라고 볼 수 있는데, 어플리케이션 오브젝트 사이의 관계를 관리하기 위한 DI와는 조금 성격이 다르다.

테스트 클래스의 컨텍스트 공유

하나의 테스트 클래스 안에서 공유할 뿐만 아니라, 여러 개의 테스트 클래스가 있는데 모두 같은 어플리케이션 컨텍스트를 사용한다면, 스프링은 테스트 클래스 사이에서도 어플리케이션 컨텍스트를 공유하게 해준다.

@AutoWired

@AutoWired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다. 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다. 별도의 DI설정 없이 타입정보를 이용해 빈을 자동으로 가져올 수 있는데, 이를 자동와이어링(AutoWiring) 이라고 한다. 하지만 테스트 코드에서는 어플리케이션 컨텍스트에 정의된 빈이 아니라, 어플리케이션 컨텍스트가 DI되었다. 스프링 어플리케이션 컨텍스트는 초기화할 때 자기 자신도 빈으로 등록하기 때문에 가능한 일이다.

@Autowired
SimpleDriverDataSource dataSource;

@AutoWired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾는데, SimpleDriverDataSource 클래스 타입은 물론이고, 인터페이스인 DataSource타입으로 변수를 선언해도 된다. 단, 두개 이상의 공통된 타입의 빈이 있는 경우에는 변수의 이름과 같은 빈을 주입한다.

웬만하면, 테스트에서도 가능한 한 인터페이스를 사용해서 어플리케이션 코드와 느슨하게 연결해두는 편이 좋다.

DI와 테스트

인터페이스를 두고 DI를 적용해야 하는 이유이다. 1. 소프트웨어 개발에서 절때로 바뀌지 않는 것은 없다. 2. 다른 차원의 서비스 기능을 도입할 수 있다. 3. 효울적인 테스트를 손쉽게 만들 수 있다.

테스트 코드에 의한 DI

DI는 어플리케이션 컨텍스트 같은 스프링 컨테이너에서만 할 수 있는 작업이 아니다. UserDao에는 DI컨테이너가 의존관계 주입에 사용하도록 수정자 메소드를 만들어 두었는데, 이 수정자 메소드는 평범한 자바 메소드이므로 테스트 코드에서도 얼마든지 호출해서 사용할 수 있다.

어플리케이션이 이용할 DAO설정을 테스트 코드에서 쓰는 것은 위험하다. 테스트 코드에 의한 DI를 이용해서 테스트 중에 DAO가 사용할 DataSource오브젝트를 바꿔주는 방법을 이용하면 된다.


...
@DirtiesContext 
public class UserDaoTest{
    @Autowired
    UserDao dao;

    @Before
    public void setUp(){
        ...
        DataSource dataSource = new SingleConnectionDataSource(
                "jdbc:mysql://localhost/testdb","spring","book",true);
        dao.setDataSource(dataSource);
    }
}

이 방법의 장점은 xml설정파일을 수정하지 않고도 테스트 코드를 통해 오브젝트 관계를 재구성할 수 있다는 것이다.

하지만 이미 어플리케이션 컨텍스트에서 가져온 설정정보에 따라 구성한 오브젝트를 가져와 의존관계를 강제로 변경했기 때문에 주의해서 사용해야 한다.

그래서 @DirtiesContext라는 어노테이션을 추가해줬다. 이 어노테이션은 스프링의 테스트 컨텍스트 프레임워크에게 해당 클래스의 테스트에서 어플리케이션 컨텍스트의 상태를 변경한다는 것을 알려준다. 이 어노테이션이 붙은 테스트 클래스에는 어플리케이션 컨텍스트 공유를 허용하지 않는다.

테스트를 위한 별도의 DI 설정

테스트 코드에서 빈 오브젝트에 수동으로 DI하는 방법은 장점보다 단점이 많다. 다른 방법으로는 아예 테스트에서 사용될 DataSource클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법을 이용해도 된다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/junitContext.xml")
public class JUnitTest {

테스트 환경에 적합한 구성을 가진 설정파일을 이용해서 테스트를 진행하면 된다.

컨테이너 없는 DI 테스트

마지막으로 살펴볼 DI를 테스트에 이용하는 방법은 아예 스프링 컨테이너를 사용하지 않고 테스트를 만드는 것이다. 이게 가능한 이유는 UserDaoDataSource 구현 클래스 어디에도 스프링의 API를 직접 사용한다거나 어플리케이션 컨텍스트를 이용한다는 코드는 존재하지 않기 때문이다. (즉 스프링 DI컨테이너에 의존하지 않는다.)

public class UserDaoTest {
    private UserDao dao;

    @Before
    public void setUp(){

        dao = new UserDao();

        DataSource dataSource = new SingleConnectionDataSource("jdbc:mysql://localhost/testdb","root","spring", true);
        dao.setDataSource(dataSource);

    }
    ...
}

테스트를 위한 DataSource를 직접 만드는 번거로움은 있지만 어플리케이션 컨텍스트를 아예 사용하지 않으니 코드는 더 단순해지고 이해하기 편해졌다.

이 테스트는 이제까지 만들었던 모든 테스트를 완벽하게 통과한다. 이유는 UserDao가 스프링 API에 의존하지 않고 자신의 관심에만 집중해서 깔끔하게 만들어진 코드이기 때문에 가능한 일이다. 그리고, DI를 적용했기 때문에 이런 가볍고 깔끔한 테스트를 만들 수 있는 것이다.

DI가 적용된 코드는 테스트에서도 다양한 방식으로 활용할 수 있을 만큼 유연하다. 어디에 DI를 적용할지 고민되는 경우, 효과적인 테스트를 만들기 위해서는 어떤 필요가 있을지를 생각해보면 된다.

DI를 이용한 테스트 방법의 선택

어느 방법을 택해야 할까 ?

항상 스프링 컨테이너 없이 테스트할 수 있는 방법을 가장 우선적으로 고려하자.

복잡한 의존관계를 가지고 있는 오브젝트들을 테스트 해야 한다면 스프링의 설정을 이용한 DI 방식의 테스트를 이용하면 편리하다.